package com.cocotingo.snail.template;

import java.awt.Color;
import java.awt.Font;
import java.awt.GraphicsConfiguration;
import java.awt.image.BufferedImage;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;

import javax.imageio.ImageIO;

import com.cocotingo.snail.Image;
import com.cocotingo.snail.Image.ImageSourceLoader;
import com.cocotingo.snail.ViewGraphics;
import com.cocotingo.snail.ResizableImage;
import com.cocotingo.snail.View;
import com.cocotingo.snail.template.annotations.DesignatedSetter;
import com.cocotingo.snail.template.annotations.Handler;
import com.cocotingo.snail.template.annotations.Outlet;
import com.cocotingo.snail.template.models.Attribute;
import com.cocotingo.snail.template.models.HandlerNode;
import com.cocotingo.snail.template.models.LayoutFactorNode;
import com.cocotingo.snail.template.models.LayoutFactorNode.AbsoluteLayoutFactorNode;
import com.cocotingo.snail.template.models.LayoutFactorNode.FittingLayoutFactorNode;
import com.cocotingo.snail.template.models.LayoutFactorNode.NeighborLayoutFactorNode;
import com.cocotingo.snail.template.models.LayoutFactorNode.ReferencedLayoutFactorNode;
import com.cocotingo.snail.template.models.LayoutFactorPair;
import com.cocotingo.snail.template.models.PaintFactorNode;
import com.cocotingo.snail.template.models.PaintNode;
import com.cocotingo.snail.template.models.ResourceNode;
import com.cocotingo.snail.template.models.ResourcesNode;
import com.cocotingo.snail.template.models.TemplateDefinitionsNode;
import com.cocotingo.snail.template.models.TemplateNode;
import com.cocotingo.snail.template.models.VariableNode;
import com.cocotingo.snail.template.models.VariablesNode;
import com.cocotingo.snail.template.models.ViewNode;
import com.cocotingo.snail.template.parser.AttributeParser;
import com.cocotingo.snail.template.parser.AttributeParsers;
import com.cocotingo.snail.template.parser.XMLTemplateParser;

public class TemplateLoader {
	
	public static interface ResourceLoader {
		public InputStream loadResourceByName(String name);
	}
	
	public static class ClassResourceLoader implements ResourceLoader {
		private final Class<?> clazz;
		public ClassResourceLoader(Class<?> resClass) {
			this.clazz = resClass;
		}
		@Override
		public InputStream loadResourceByName(String name) {
			return clazz.getResourceAsStream(name);
		}
	}
	
	private static class LoaderContext {
		
		private class TemplateImageResourceLoader implements ImageSourceLoader {

			private final String resourceName;
			
			TemplateImageResourceLoader(String resourceName) {
				this.resourceName = resourceName;
			}
			
			@Override
			public BufferedImage loadImage() {
				InputStream stream = loader.loadResourceByName(resourceName);
				try {
					return ImageIO.read(stream);
				} catch (IOException e) {
					return null;
				}
			}

			@Override
			public void unloadImage(BufferedImage image) {
				image.flush();
			}
			
		}
		
		final ResourceLoader loader;
		final Map<String, TemplateNode> templates;
		final Map<TemplateNode, List<LayoutFactorPair>> sortedLayoutFactorPairs;
		
		LoaderContext(ResourceLoader loader) {
			this.loader = loader;
			templates = new HashMap<String, TemplateNode>();
			sortedLayoutFactorPairs = new HashMap<TemplateNode, List<LayoutFactorPair>>();
		}
		
		ImageSourceLoader createImageSourceLoader(String resName) {
			return new TemplateImageResourceLoader(resName);
		}
		
	}
	
	private static interface RuntimeValueGetter {
		int getValueType();
		Object getValue();
	}
	
	private abstract static class IntegerRuntimeValueGetter implements RuntimeValueGetter {
		public int getValueType() {
			return VariableNode.INTEGER;
		}
	}
	
	private static class RuntimeEventHandler implements EventHandler {
		private final Method callback;
		private final TemplateView rootView;
		RuntimeEventHandler(Method callback, TemplateView rootView) {
			this.callback = callback;
			this.rootView = rootView;
		}
		@Override
		public void handle(TemplateView sender, String eventName, Object arg) {
			try {
				callback.invoke(rootView, sender, arg);
			} catch (Exception e) {
				throw new TemplateRuntimeException(e);
			}
		}
	}
	
	private static class PaintImageHandler implements EventHandler {
		
		private final Image image;
		private final int style;
		
		PaintImageHandler(Image image, int style) {
			this.image = image;
			this.style = style;
		}

		@Override
		public void handle(TemplateView sender, String eventName, Object arg) {
			ViewGraphics g = (ViewGraphics) arg;
			switch (style) {
			case PaintFactorNode.PaintImageNode.FILL:
				g.drawImage(image, 0, 0, sender.getWidth(), sender.getHeight());
				break;
				
			case PaintFactorNode.PaintImageNode.TILE:
				for (int y = 0; y < sender.getHeight(); y += image.height) {
					for (int x = 0; x < sender.getWidth(); x += image.width) {
						g.drawImage(image, x, y);
					}
				}
				break;
				
			case PaintFactorNode.PaintImageNode.CENTER:
				g.drawImage(image, (sender.getWidth() - image.width) / 2, (sender.getHeight() - image.height) / 2);
				break;
			}
		}
		
	}
	
	public static final String VERSION = "2.0";
	private static final Map<Class<?>, AttributeParser> attributeParsers;
	
	static {
		attributeParsers = new HashMap<Class<?>, AttributeParser>();
		Set<AttributeParser> parsers = AttributeParsers.getDefaultParsers();
		for (AttributeParser parser : parsers) {
			registerAttributeParser(parser);
		}
	}
	
	public static AttributeParser registerAttributeParser(AttributeParser parser) {
		return attributeParsers.put(parser.getResultType(), parser);
	}

	public static boolean unregisterAttributeParser(AttributeParser parser) {
		if (attributeParsers.containsValue(parser)) {
			attributeParsers.remove(parser.getResultType());
			return true;
		} else {
			return false;
		}
	}

	public static boolean canParseAttributeType(Class<?> clazz) {
		return attributeParsers.containsKey(clazz);
	}
	
	public static AttributeParser getAttributeParser(Class<?> clazz) {
		return attributeParsers.get(clazz);
	}
	
	//// Instance Methods ////

	private abstract class LayoutEventHandler implements EventHandler {
		private final RuntimeValueGetter getter1, getter2;
		LayoutEventHandler(RuntimeValueGetter getter1, RuntimeValueGetter getter2) {
			this.getter1 = getter1;
			this.getter2 = getter2;
		}
		@Override
		public void handle(TemplateView sender, String eventName, Object arg) {
			handle((Integer) getter1.getValue(), (Integer) getter2.getValue());
		}
		abstract protected void handle(int v1, int v2);
	}

	private XMLTemplateParser parser;
	private final GraphicsConfiguration configuration;
	private final Map<String, Object> resources;
	private final List<LoaderContext> contexts;

	public TemplateLoader(GraphicsConfiguration gc) {
		parser = null;
		if (gc == null) throw new IllegalArgumentException("GraphicsConfiguration shouldn't be null");
		configuration = gc;
		resources = new HashMap<String, Object>();
		contexts = new ArrayList<LoaderContext>();
	}
	
	public final GraphicsConfiguration getConfiguration() {
		return configuration;
	}
	
	public final Image getResourceAsImage(String id) {
		return (Image) resources.get(id);
	}
	
	public final ResizableImage getResourceAsResizableImage(String id) {
		return (ResizableImage) resources.get(id);
	}
	
	public final Color getResourceAsColor(String id) {
		return (Color) resources.get(id);
	}
	
	public final Font getResourceAsFont(String id) {
		return (Font) resources.get(id);
	}
	
	public final Class<?> getResourceAsClass(String id) {
		return (Class<?>) resources.get(id);
	}
	
	public final void load(String xmlFileName, Class<?> clazz) throws TemplateParserException, TemplateSyntaxException, TemplateSemanticException {
		ClassResourceLoader loader = new ClassResourceLoader(clazz);
		this.load(xmlFileName, loader);
	}
	
	public void load(String xmlFileName, ResourceLoader loader) throws TemplateParserException, TemplateSyntaxException, TemplateSemanticException {
		if (xmlFileName == null) throw new IllegalArgumentException("XML resource name shouldn't be null");
		if (loader == null) throw new IllegalArgumentException("ResourceLoader shouldn't be null");
		InputStream xmlStream = loader.loadResourceByName(xmlFileName);
		if (parser == null) {
			parser = new XMLTemplateParser();
		}
		TemplateDefinitionsNode node = parser.parse(xmlStream);
		node.validate();
		if (!node.getVersion().equals(VERSION)) {
			throw new TemplateSemanticException("incorrect template version: " + node.getVersion());
		}
		LoaderContext context = new LoaderContext(loader);
		contexts.add(context);
		loadResources(context, node);
		loadTemplates(context, node);
	}
	
	private void loadResources(LoaderContext context, TemplateDefinitionsNode rootNode) throws TemplateSemanticException {
		for (ResourcesNode resourcesNode : rootNode.resourcesNodes()) {
			for (ResourceNode node : resourcesNode.resourceNodes()) {
				String id = node.getID();
				if (resources.containsKey(id)) throw new TemplateSemanticException("re-defined resource: " + id);
				Object resObject = null;
				switch (node.getType()) {
				case ResourceNode.IMAGE:
					resObject = new Image(configuration, context.createImageSourceLoader(((ResourceNode.ImageNode) node).getResourceName()));
					break;
					
				case ResourceNode.RESIZABLE_IMAGE:
					{
						ResourceNode.ResizableImageNode sn = (ResourceNode.ResizableImageNode) node;
						resObject = new ResizableImage(
								configuration,
								context.createImageSourceLoader(sn.getResourceName()),
								sn.getX1(), sn.getY1(), sn.getX2(), sn.getY2());
					}
					break;
					
				case ResourceNode.COLOR:
					resObject = new Color(((ResourceNode.ColorNode) node).getColor());
					break;
					
				case ResourceNode.FONT:
					{
						ResourceNode.FontNode fn = (ResourceNode.FontNode) node;
						resObject = new Font(fn.getFontName(), fn.getFontStyle(), fn.getFontSize());
					}
					break;
				
				case ResourceNode.CLASS:
					{
						ResourceNode.ClassNode cn = (ResourceNode.ClassNode) node;
						try {
							resObject = Class.forName(cn.getClassName());
						} catch (ClassNotFoundException e) {
							throw new TemplateSemanticException(e);
						}
					}
					break;
				
				default:
					throw new TemplateSemanticException("unknown resource type: " + node.getType());
				}
				resources.put(id, resObject);
			}
		}
	}
	
	private void loadTemplates(LoaderContext context, TemplateDefinitionsNode rootNode) throws TemplateSemanticException {
		for (TemplateNode node : rootNode.templateNodes()) {
			if (getContext(node.getID()) == null) {
				context.templates.put(node.getID(), node);
				try {
					context.sortedLayoutFactorPairs.put(node, node.createSortedLayoutFactorPairs());
				} catch (TemplateSyntaxException e) {
					throw new TemplateSemanticException("incorrect layout factors", e);
				}
			} else {
				throw new TemplateSemanticException("re-defined template: " + node.getID());
			}
		}
	}
	
	private LoaderContext getContext(String templateID) {
		for (LoaderContext c : contexts) {
			if (c.templates.containsKey(templateID)) return c;
		}
		return null;
	}
	
	//// Instance Methods ////
	
	private class MakerContext {
		
		final LoaderContext loaderContext;
		final String templateID;
		TemplateView rootView;
		final List<TemplateView> allViews;
		final Map<String, TemplateView> viewMap;
		final Map<String, RuntimeValueGetter> varMap;
		Map<String, Field> unbindVars;
		Map<String, Method> unbindCallbacks;
		Map<String, Method> callbacks;
		
		MakerContext(LoaderContext loaderContext, String templateID) {
			this.templateID = templateID;
			this.loaderContext = loaderContext;
			this.rootView = null;
			this.unbindVars = null;
			this.unbindCallbacks = null;
			this.callbacks = null;
			allViews = new LinkedList<TemplateView>(); // records all the views created
			viewMap = new HashMap<String, TemplateView>(); // records the views with id
			varMap = new HashMap<String, RuntimeValueGetter>();
		}
		
		boolean hasSameID(String id) {
			return TemplateLoader.this.resources.containsKey(id) ||
					varMap.containsKey(id) ||
					viewMap.containsKey(id);
		}
		
	}

	public final TemplateView makeView(String templateID) throws TemplateRuntimeException {
		return this.makeView(null, templateID);
	}
	
	public TemplateView makeView(TemplateView view, String templateID) throws TemplateRuntimeException {
		LoaderContext loaderContext = getContext(templateID);
		if (loaderContext == null) throw new TemplateRuntimeException("can not find template named: " + templateID);
		TemplateNode template = loaderContext.templates.get(templateID);
		MakerContext context = new MakerContext(loaderContext, templateID);
		
		// create view structure
		ViewNode rootViewNode = template.getRootViewNode();
		context.rootView = createView(rootViewNode, view, context);
		
		// prepare binding
		final boolean needs_binding = (rootViewNode.getClassName() != null && !rootViewNode.getClassName().isEmpty());
		if (needs_binding) {
			context.unbindVars = new HashMap<String, Field>();
			for (Field field : ReflectionCache.getInstance().getAllTemplateOutlets(context.rootView.getClass())) {
				context.unbindVars.put(field.getName(), field);
			}
			context.unbindCallbacks = new HashMap<String, Method>();
			context.callbacks = new HashMap<String, Method>();
			for (Method method : ReflectionCache.getInstance().getAllTemplateHandlers(context.rootView.getClass())) {
				context.unbindCallbacks.put(method.getName(), method);
				context.callbacks.put(method.getName(), method);
			}
		}
		
		// binding template variables
		VariablesNode varsNode = template.getVariablesNode();
		if (varsNode != null) {
			createVariableGetters(varsNode, context, needs_binding);
		}
		
		// binding template view variables
		if (needs_binding) {
			bindingTemplateVariables(context.rootView, context);
		}
		
		// check var binding
		if (needs_binding) {
			if (context.unbindVars.size() > 0) {
				throw new TemplateRuntimeException("can not bind template variable: " + context.unbindVars.keySet().iterator().next());
			}
		}
		
		// setting view
		buildView(rootViewNode, context.allViews.iterator(), context);
		
		// check callback binding
		if (needs_binding) {
			if (context.unbindCallbacks.size() > 0) {
				throw new TemplateRuntimeException("can not bind template callback: " + context.unbindCallbacks.keySet().iterator().next());
			}
		}
		
		// layout
		buildAutoLayoutHandlers(context);
		
		context.rootView.createdFromTemplate(this);
		return context.rootView;
	}
	
	private void createVariableGetters(VariablesNode varsNode, MakerContext context, boolean binding) throws TemplateRuntimeException {
		if (binding) {
			for (VariableNode<?> varNode : varsNode.variableNodes()) {
				String id = varNode.getID();
				Field field = context.unbindVars.get(id);
				if (field != null) {
					createBindingVariableGetter(varNode, context, field);
					context.unbindVars.remove(id);
				} else {
					createDefaultVariableGetter(varNode, context);
				}
			}
		} else {
			for (VariableNode<?> varNode : varsNode.variableNodes()) {
				createDefaultVariableGetter(varNode, context);
			}
		}
	}
	
	private void createBindingVariableGetter(VariableNode<?> varNode, MakerContext context, final Field field) throws TemplateRuntimeException {
		String id = varNode.getID();
		if (id != null && !id.isEmpty()) {
			if (!context.hasSameID(id)) {
				final int type = varNode.getType();
				final Object default_value = varNode.getDefaultValue();
				final TemplateView rootView = context.rootView;
				// set initial value
				try {
					field.set(rootView, default_value);
				} catch (IllegalArgumentException e) {
					throw new TemplateRuntimeException("can not bind template variable: " + id, e);
				} catch (IllegalAccessException e) {
					throw new TemplateRuntimeException("can not bind template variable: " + id, e);
				}
				context.varMap.put(id, new RuntimeValueGetter() {
					@Override
					public int getValueType() {
						return type;
					}
					@Override
					public Object getValue() {
						try {
							return field.get(rootView);
						} catch (IllegalArgumentException e) {
							return default_value;
						} catch (IllegalAccessException e) {
							return default_value;
						}
					}
				});
			} else {
				throw new TemplateRuntimeException("re-defined id: " + id);
			}
		}
	}
	
	private void createDefaultVariableGetter(VariableNode<?> varNode, MakerContext context) throws TemplateRuntimeException {
		String id = varNode.getID();
		if (id != null && !id.isEmpty()) {
			if (!context.hasSameID(id)) {
				final int type = varNode.getType();
				final Object default_value = varNode.getDefaultValue();
				context.varMap.put(id, new RuntimeValueGetter() {
					@Override
					public int getValueType() {
						return type;
					}
					@Override
					public Object getValue() {
						return default_value;
					}
				});
			} else {
				throw new TemplateRuntimeException("re-defined id: " + id);
			}
		}
	}
	
	private TemplateView createView(ViewNode node, TemplateView view, MakerContext context) throws TemplateRuntimeException {
		String className = node.getClassName();
		String templateName = node.getTemplateName();
		if (view == null) {
			// create a view
			if (className != null && !className.isEmpty()) {
				Class<?> viewClass = getResourceAsClass(className);
				if (viewClass == null) throw new TemplateRuntimeException("can not find class: " + className);
				try {
					view = (TemplateView) viewClass.newInstance();
				} catch (InstantiationException e) {
					throw new TemplateRuntimeException("can not create view by class name: " + className, e);
				} catch (IllegalAccessException e) {
					throw new TemplateRuntimeException("can not create view by class name: " + className, e);
				} catch (ClassCastException e) {
					throw new TemplateRuntimeException("the class name is not a subclass of TemplateView: " + className, e);
				}
			} else if (templateName != null && !templateName.isEmpty()) {
				view = makeView(templateName);
			} else {
				view = new TemplateView();
			}
		} else {
			// check type
			if (className != null && !className.isEmpty()) {
				Class<?> viewClass = getResourceAsClass(className);
				if (viewClass == null) throw new TemplateRuntimeException("can not find class: " + className);
				if (!viewClass.isInstance(view)) {
					throw new TemplateRuntimeException("the view is not an instance of " + className);
				}
			} else if (templateName != null && !templateName.isEmpty()) {
				makeView(view, templateName);
			}
		}
		
		// record view and id
		context.allViews.add(view);
		String id = node.getID();
		if (id != null && !id.isEmpty()) {
			if (!context.hasSameID(id)) {
				context.viewMap.put(id, view);
			} else {
				throw new TemplateRuntimeException("re-defined id: " + id);
			}
		}
		
		// create subviews
		for (ViewNode viewNode : node.viewNodes()) {
			TemplateView subview = createView(viewNode, null, context);
			view.addSubview(subview);
		}
		
		return view;
	}
	
	private void buildView(ViewNode node, Iterator<TemplateView> it, MakerContext context) throws TemplateRuntimeException {
		final TemplateView view = it.next();
		
		// set view attributes
		setViewAttributes(node, view, context.loaderContext);
		
		// handlers
		HandlerNode handlerNode = node.getHandlerNode();
		if (handlerNode != null) {
			for (String event : handlerNode.eventNames()) {
				String handler = handlerNode.getHandlerName(event);
				Method callback = context.callbacks.get(handler);
				if (callback == null) throw new TemplateRuntimeException("can not find template callback named: " + handler);
				else if (callback.getParameterTypes().length != 2) {
					throw new TemplateRuntimeException("incorrect parameters of template callback: " + handler);
				} else {
					view.addEventHandler(event, new RuntimeEventHandler(callback, context.rootView));
					context.unbindCallbacks.remove(handler);
				}
			}
		}
		
		// paint
		PaintNode paintNode = node.getPaintNode();
		if (paintNode != null) {
			for (PaintFactorNode factorNode : paintNode.factors()) {
				switch (factorNode.getType()) {
				case PaintFactorNode.IMAGE:
					buildPaintImage((PaintFactorNode.PaintImageNode) factorNode, view);
					break;

				default:
					throw new TemplateRuntimeException("nnknown paint factor type: " + factorNode.getType());
				}
			}
		}
		
		for (ViewNode n : node.viewNodes()) {
			buildView(n, it, context);
		}
	}
	
	private void bindingTemplateVariables(TemplateView rootView, MakerContext context) throws TemplateRuntimeException {
		for (String id : context.viewMap.keySet()) {
			Field field = context.unbindVars.get(id);
			if (field != null) {
				try {
					if (field.get(rootView) != null) {
						throw new TemplateRuntimeException("template variable is not null before binding: " + id);
					}
					field.set(rootView, context.viewMap.get(id));
				} catch (IllegalArgumentException e) {
					throw new TemplateRuntimeException("can not bind template variable: " + id, e);
				} catch (IllegalAccessException e) {
					throw new TemplateRuntimeException("can not bind template variable: " + id, e);
				}
				context.unbindVars.remove(id);
			}
		}
	}

	private void setViewAttributes(ViewNode node, TemplateView view, LoaderContext context) throws TemplateRuntimeException {
		for (Attribute attr : node.attributes()) {
			Method[] setters = ReflectionCache.getInstance().findSetters(view.getClass(), attr.getName());
			if (setters.length == 0) throw new TemplateRuntimeException("can not find proper setter for attribute: " + attr.getName());
			else if (setters.length > 1) throw new TemplateRuntimeException("multiple setters found for attribute: " + attr.getName() + ", use @DesignatedSetter annotaion to specify a setter.");
			Method setter = setters[0];
			Class<?> paraType = setter.getParameterTypes()[0];
			AttributeParser parser = attributeParsers.get(paraType);
			if (parser == null) throw new TemplateRuntimeException("missing attribute parser: " + attr.getName());
			Object value = parser.parse(this, attr.getValue());
			// invoke
			try {
				setter.invoke(view, value);
			} catch (IllegalArgumentException e) {
				throw new TemplateRuntimeException("cannot set attribute: " + attr.getName(), e);
			} catch (IllegalAccessException e) {
				throw new TemplateRuntimeException("cannot set attribute: " + attr.getName(), e);
			} catch (InvocationTargetException e) {
				throw new TemplateRuntimeException("cannot set attribute: " + attr.getName(), e);
			}
		}
	}

	private void buildPaintImage(PaintFactorNode.PaintImageNode node, TemplateView view) throws TemplateRuntimeException {
		Image img = getResourceAsImage(node.getImageID());
		if (img == null) throw new TemplateRuntimeException("can not find image resource named: " + node.getImageID());
		view.addEventHandler(TemplateView.REPAINT, new PaintImageHandler(img, node.getStyle()));
	}

	//// Layout ////
	
	private void buildAutoLayoutHandlers(MakerContext context) throws TemplateRuntimeException {
		TemplateNode template = context.loaderContext.templates.get(context.templateID);
		List<LayoutFactorPair> pairs = context.loaderContext.sortedLayoutFactorPairs.get(template);
		for (LayoutFactorPair pair : pairs) {
			TemplateView targetView = context.allViews.get(pair.getViewIndex());
			LayoutFactorNode factor1 = pair.getFactor1();
			LayoutFactorNode factor2 = pair.getFactor2();
			if (factor1.getFactorType() == factor2.getFactorType()) {
				throw new TemplateRuntimeException("duplicate layout factors in one layout node: " + factor1.getFactorType());
			}
			EventHandler handler = null;
			if (pair.isHorizentalPair()) {
				if (factor1.getFactorType() > factor2.getFactorType()) {
					handler = buildHorizentalLayoutHandler(targetView, factor2, factor1, context);
				} else {
					handler = buildHorizentalLayoutHandler(targetView, factor1, factor2, context);
				}
			} else {
				if (factor1.getFactorType() > factor2.getFactorType()) {
					handler = buildVerticalLayoutHandler(targetView, factor2, factor1, context);
				} else {
					handler = buildVerticalLayoutHandler(targetView, factor1, factor2, context);
				}
			}
			context.rootView.addEventHandler(TemplateView.AUTO_LAYOUT, handler);
		}
	}
	
	private EventHandler buildHorizentalLayoutHandler(final TemplateView targetView, LayoutFactorNode factor1, LayoutFactorNode factor2, MakerContext context) throws TemplateRuntimeException {
		RuntimeValueGetter getter1 = getRuntimeValueGetterFromLayoutFactor(targetView, factor1, context);
		RuntimeValueGetter getter2 = getRuntimeValueGetterFromLayoutFactor(targetView, factor2, context);
		int t1 = factor1.getFactorType();
		int t2 = factor2.getFactorType();
		if (t1 == LayoutFactorNode.LEFT && t2 == LayoutFactorNode.CENTER_LEFT) {
			return new LayoutEventHandler(getter1, getter2) {
				@Override
				protected void handle(int v1, int v2) {
					targetView.setLeft(v1);
					targetView.setWidth(2 * (v2 - v1));
				}
			};
		} else if (t1 == LayoutFactorNode.LEFT && t2 == LayoutFactorNode.RIGHT) {
			return new LayoutEventHandler(getter1, getter2) {
				@Override
				protected void handle(int v1, int v2) {
					View.scaleViewWithLeftAndRight(targetView, v1, v2);
				}
			};
		} else if (t1 == LayoutFactorNode.LEFT && t2 == LayoutFactorNode.WIDTH) {
			return new LayoutEventHandler(getter1, getter2) {
				@Override
				protected void handle(int v1, int v2) {
					targetView.setLeft(v1);
					targetView.setWidth(v2);
				}
			};
		} else if (t1 == LayoutFactorNode.CENTER_LEFT && t2 == LayoutFactorNode.RIGHT) {
			return new LayoutEventHandler(getter1, getter2) {
				@Override
				protected void handle(int v1, int v2) {
					int width = 2 * (targetView.getSuperView().getWidth() - v1 - v2);
					targetView.setLeft(v1 - width / 2);
					targetView.setWidth(width);
				}
			};
		} else if (t1 == LayoutFactorNode.CENTER_LEFT && t2 == LayoutFactorNode.WIDTH) {
			return new LayoutEventHandler(getter1, getter2) {
				@Override
				protected void handle(int v1, int v2) {
					targetView.setLeft(v1 - v2 / 2);
					targetView.setWidth(v2);
				}
			};
		} else if (t1 == LayoutFactorNode.RIGHT && t2 == LayoutFactorNode.WIDTH) {
			return new LayoutEventHandler(getter1, getter2) {
				@Override
				protected void handle(int v1, int v2) {
					targetView.setWidth(v2);
					View.putViewWithRight(targetView, v1);
				}
			};
		} else {
			throw new TemplateRuntimeException();
		}
	}
	
	private EventHandler buildVerticalLayoutHandler(final TemplateView targetView, LayoutFactorNode factor1, LayoutFactorNode factor2, MakerContext context) throws TemplateRuntimeException {
		RuntimeValueGetter getter1 = getRuntimeValueGetterFromLayoutFactor(targetView, factor1, context);
		RuntimeValueGetter getter2 = getRuntimeValueGetterFromLayoutFactor(targetView, factor2, context);
		int t1 = factor1.getFactorType();
		int t2 = factor2.getFactorType();
		if (t1 == LayoutFactorNode.TOP && t2 == LayoutFactorNode.CENTER_TOP) {
			return new LayoutEventHandler(getter1, getter2) {
				@Override
				protected void handle(int v1, int v2) {
					targetView.setTop(v1);
					targetView.setHeight(2 * (v2 - v1));
				}
			};
		} else if (t1 == LayoutFactorNode.TOP && t2 == LayoutFactorNode.BOTTOM) {
			return new LayoutEventHandler(getter1, getter2) {
				@Override
				protected void handle(int v1, int v2) {
					View.scaleViewWithTopAndBottom(targetView, v1, v2);
				}
			};
		} else if (t1 == LayoutFactorNode.TOP && t2 == LayoutFactorNode.HEIGHT) {
			return new LayoutEventHandler(getter1, getter2) {
				@Override
				protected void handle(int v1, int v2) {
					targetView.setTop(v1);
					targetView.setHeight(v2);
				}
			};
		} else if (t1 == LayoutFactorNode.CENTER_TOP && t2 == LayoutFactorNode.BOTTOM) {
			return new LayoutEventHandler(getter1, getter2) {
				@Override
				protected void handle(int v1, int v2) {
					int height = 2 * (targetView.getSuperView().getHeight() - v1 - v2);
					targetView.setTop(v1 - height / 2);
					targetView.setHeight(height);
				}
			};
		} else if (t1 == LayoutFactorNode.CENTER_TOP && t2 == LayoutFactorNode.HEIGHT) {
			return new LayoutEventHandler(getter1, getter2) {
				@Override
				protected void handle(int v1, int v2) {
					targetView.setTop(v1 - v2 / 2);
					targetView.setHeight(v2);
				}
			};
		} else if (t1 == LayoutFactorNode.BOTTOM && t2 == LayoutFactorNode.HEIGHT) {
			return new LayoutEventHandler(getter1, getter2) {
				@Override
				protected void handle(int v1, int v2) {
					targetView.setHeight(v2);
					View.putViewWithBottom(targetView, v1);
				}
			};
		} else {
			throw new TemplateRuntimeException();
		}
	}
	
	private RuntimeValueGetter getRuntimeValueGetterFromLayoutFactor(final TemplateView targetView, LayoutFactorNode factor, MakerContext context) throws TemplateRuntimeException {
		final RuntimeValueGetter intGetter = getIntegerValueGetterFromLayoutFactor(factor, context);
		final RuntimeValueGetter doubleGetter = getDoubleValueGetterFromLayoutFactor(factor, context);
		final TemplateView referencedView = getViewArgumentFromLayoutFactor(factor, context);
		switch (factor.getLayoutType()) {
		case LayoutFactorNode.LAYOUT_ABSOLUTE:
			return intGetter;
		case LayoutFactorNode.LAYOUT_REFERENCED:
			switch (factor.getFactorType()) {
			case LayoutFactorNode.LEFT:
				return new IntegerRuntimeValueGetter() {
					@Override
					public Object getValue() {
						return (int) (referencedView.getLeft() * (Double) doubleGetter.getValue());
					}
				};
			case LayoutFactorNode.RIGHT:
				return new IntegerRuntimeValueGetter() {
					@Override
					public Object getValue() {
						return (int) (referencedView.getRight() * (Double) doubleGetter.getValue());
					}
				};
			case LayoutFactorNode.CENTER_LEFT:
				return new IntegerRuntimeValueGetter() {
					@Override
					public Object getValue() {
						return (int) ((referencedView.getLeft() + referencedView.getWidth() / 2.0) * (Double) doubleGetter.getValue());
					}
				};
			case LayoutFactorNode.WIDTH:
				return new IntegerRuntimeValueGetter() {
					@Override
					public Object getValue() {
						return (int) (referencedView.getWidth() * (Double) doubleGetter.getValue());
					}
				};
			case LayoutFactorNode.TOP:
				return new IntegerRuntimeValueGetter() {
					@Override
					public Object getValue() {
						return (int) (referencedView.getTop() * (Double) doubleGetter.getValue());
					}
				};
			case LayoutFactorNode.BOTTOM:
				return new IntegerRuntimeValueGetter() {
					@Override
					public Object getValue() {
						return (int) (referencedView.getBottom() * (Double) doubleGetter.getValue());
					}
				};
			case LayoutFactorNode.CENTER_TOP:
				return new IntegerRuntimeValueGetter() {
					@Override
					public Object getValue() {
						return (int) ((referencedView.getTop() + referencedView.getHeight() / 2.0) * (Double) doubleGetter.getValue());
					}
				};
			case LayoutFactorNode.HEIGHT:
				return new IntegerRuntimeValueGetter() {
					@Override
					public Object getValue() {
						return (int) (referencedView.getHeight() * (Double) doubleGetter.getValue());
					}
				};
			default:
				throw new TemplateRuntimeException();
			}
		case LayoutFactorNode.LAYOUT_NEIGHBOR:
			switch (factor.getFactorType()) {
			case LayoutFactorNode.LEFT:
				return new IntegerRuntimeValueGetter() {
					@Override
					public Object getValue() {
						return referencedView.getLeft() + referencedView.getWidth() + (Integer) intGetter.getValue();
					}
				};
			case LayoutFactorNode.TOP:
				return new IntegerRuntimeValueGetter() {
					@Override
					public Object getValue() {
						return referencedView.getTop() + referencedView.getHeight() + (Integer) intGetter.getValue();
					}
				};
			case LayoutFactorNode.RIGHT:
				return new IntegerRuntimeValueGetter() {
					@Override
					public Object getValue() {
						return referencedView.getRight() + referencedView.getWidth() + (Integer) intGetter.getValue();
					}
				};
			case LayoutFactorNode.BOTTOM:
				return new IntegerRuntimeValueGetter() {
					@Override
					public Object getValue() {
						return referencedView.getBottom() + referencedView.getHeight() + (Integer) intGetter.getValue();
					}
				};
			default:
				throw new TemplateRuntimeException();
			}
		case LayoutFactorNode.LAYOUT_FITTING:
			switch (factor.getFactorType()) {
			case LayoutFactorNode.WIDTH:
				return new IntegerRuntimeValueGetter() {
					@Override
					public Object getValue() {
						int width = targetView.getWidth();
						width -= referencedView.getRight() - (Integer) intGetter.getValue();
						return width;
					}
				};
			case LayoutFactorNode.HEIGHT:
				return new IntegerRuntimeValueGetter() {
					@Override
					public Object getValue() {
						int height = targetView.getHeight();
						height -= referencedView.getBottom() - (Integer) intGetter.getValue();
						return height;
					}
				};
			default:
				throw new TemplateRuntimeException();
			}
		case LayoutFactorNode.LAYOUT_PREFERRED:
			switch (factor.getFactorType()) {
			case LayoutFactorNode.WIDTH:
				return new IntegerRuntimeValueGetter() {
					@Override
					public Object getValue() {
						return targetView.getPreferredWidth();
					}
				};
			case LayoutFactorNode.HEIGHT:
				return new IntegerRuntimeValueGetter() {
					@Override
					public Object getValue() {
						return targetView.getPreferredHeight();
					}
				};
			default:
				throw new TemplateRuntimeException();
			}
		case LayoutFactorNode.LAYOUT_CENTERED:
			switch (factor.getFactorType()) {
			case LayoutFactorNode.CENTER_LEFT:
				return new IntegerRuntimeValueGetter() {
					@Override
					public Object getValue() {
						return targetView.getSuperView().getWidth() / 2;
					}
				};
			case LayoutFactorNode.CENTER_TOP:
				return new IntegerRuntimeValueGetter() {
					@Override
					public Object getValue() {
						return targetView.getSuperView().getHeight() / 2;
					}
				};
			default:
				throw new TemplateRuntimeException();
			}
		default:
			throw new TemplateRuntimeException();
		}
	}
	
	private RuntimeValueGetter getIntegerValueGetterFromLayoutFactor(LayoutFactorNode factor, MakerContext context) throws TemplateRuntimeException {
		String value = null;
		switch (factor.getLayoutType()) {
		case LayoutFactorNode.LAYOUT_ABSOLUTE:
			value = ((AbsoluteLayoutFactorNode) factor).getValue();
			break;
		case LayoutFactorNode.LAYOUT_NEIGHBOR:
			value = ((NeighborLayoutFactorNode) factor).getMargin();
			break;
		case LayoutFactorNode.LAYOUT_FITTING:
			value = ((FittingLayoutFactorNode) factor).getMargin();
			break;
		default:
			return null;
		}
		return getIntegerValueGetter(value, context);
	}
	
	private RuntimeValueGetter getDoubleValueGetterFromLayoutFactor(LayoutFactorNode factor, MakerContext context) throws TemplateRuntimeException {
		String value = null;
		switch (factor.getLayoutType()) {
		case LayoutFactorNode.LAYOUT_REFERENCED:
			value = ((ReferencedLayoutFactorNode) factor).getMultiplier();
			break;
		default:
			return null;
		}
		return getDoubleValueGetter(value, context);
	}
	
	private TemplateView getViewArgumentFromLayoutFactor(LayoutFactorNode factor, MakerContext context) throws TemplateRuntimeException {
		String id = null;
		switch (factor.getLayoutType()) {
		case LayoutFactorNode.LAYOUT_REFERENCED:
			id = ((ReferencedLayoutFactorNode) factor).getReferencedView();
			break;
		case LayoutFactorNode.LAYOUT_NEIGHBOR:
			id = ((NeighborLayoutFactorNode) factor).getNeighborView();
			break;
		case LayoutFactorNode.LAYOUT_FITTING:
			id = ((FittingLayoutFactorNode) factor).getContentView();
			break;
		default:
			return null;
		}
		TemplateView view = context.viewMap.get(id);
		if (view == null) throw new TemplateRuntimeException("can not find view named: " + id);
		return view;
	}
	
	private RuntimeValueGetter getIntegerValueGetter(String value, MakerContext context) throws TemplateRuntimeException {
		if (value == null) value = "0";
		try {
			final Integer result = (Integer) attributeParsers.get(int.class).parse(this, value);
			return new RuntimeValueGetter() {
				@Override
				public int getValueType() {
					return VariableNode.INTEGER;
				}
				@Override
				public Object getValue() {
					return result;
				}
			};
		} catch (TemplateRuntimeException e) {
			RuntimeValueGetter getter = context.varMap.get(value);
			if (getter == null || getter.getValueType() != VariableNode.INTEGER) throw new TemplateRuntimeException("can not find integer variable named: " + value);
			return getter;
		}
	}
	
	private RuntimeValueGetter getDoubleValueGetter(String value, MakerContext context) throws TemplateRuntimeException {
		if (value == null) value = "1.0";
		try {
			final Double result = (Double) attributeParsers.get(double.class).parse(this, value);
			return new RuntimeValueGetter() {
				@Override
				public int getValueType() {
					return VariableNode.DOUBLE;
				}
				@Override
				public Object getValue() {
					return result;
				}
			};
		} catch (TemplateRuntimeException e) {
			RuntimeValueGetter getter = context.varMap.get(value);
			if (getter == null || getter.getValueType() != VariableNode.DOUBLE) throw new TemplateRuntimeException("can not find double variable named: " + value);
			return getter;
		}
	}
	
}

class ReflectionCache {
	
	private static ReflectionCache instance;
	
	static ReflectionCache getInstance() {
		if (instance == null) instance = new ReflectionCache();
		return instance;
	}
	
	private static class ClassInfo {
		
		Class<?> clazz;
		final Map<String, Method[]> settersMap;
		List<Field> template_vars;
		List<Method> template_callbacks;
		
		ClassInfo(Class<?> clazz) {
			this.clazz = clazz;
			this.settersMap = new HashMap<String, Method[]>();
			this.template_vars = null;
			this.template_callbacks = null;
		}
		
		Method[] findSetters(String setterName) {
			if (!settersMap.containsKey(setterName)) {
				ArrayList<Method> setters = new ArrayList<Method>();
				for (Method method : clazz.getMethods()) {
					if (method.getName().equals(setterName)) {
						Class<?>[] paraClass = method.getParameterTypes();
						if (paraClass.length == 1) {
							setters.add(method);
						}
					}
				}
				// filter designated setter
				ArrayList<Method> designated_setters = new ArrayList<Method>();
				for (Method method : setters) {
					DesignatedSetter annotation = method.getAnnotation(DesignatedSetter.class);
					if (annotation != null) {
						designated_setters.add(method);
					}
				}
				if (designated_setters.size() == 1) {
					settersMap.put(setterName, designated_setters.toArray(new Method[1]));
				} else {
					settersMap.put(setterName, setters.toArray(new Method[setters.size()]));
				}
			}
			return settersMap.get(setterName);
		}
		
		Iterable<Field> getAllTemplateOutlets() {
			if (template_vars == null) {
				template_vars = new ArrayList<Field>();
				Field[] fields = clazz.getFields();
				for (Field field : fields) {
					if (field.isAnnotationPresent(Outlet.class)) {
						template_vars.add(field);
					}
				}
			}
			return template_vars;
		}
		
		Iterable<Method> getAllTemplateHandlers() {
			if (template_callbacks == null) {
				template_callbacks = new ArrayList<Method>();
				Method[] methods = clazz.getMethods();
				for (Method method : methods) {
					if (method.isAnnotationPresent(Handler.class)) {
						template_callbacks.add(method);
					}
				}
			}
			return template_callbacks;
		}
		
	}
	
	private final Map<String, String> setterNamesCache;
	private final Map<Class<?>, ClassInfo> classInfos;
	
	private ReflectionCache() {
		setterNamesCache = new HashMap<String, String>();
		classInfos = new HashMap<Class<?>, ClassInfo>();
	}
	
	Method[] findSetters(Class<?> clazz, String attributeName) {
		if (!setterNamesCache.containsKey(attributeName)) {
			StringBuilder buf = new StringBuilder("set");
			buf.append(attributeName);
			buf.setCharAt(3, Character.toUpperCase(attributeName.charAt(0)));
			setterNamesCache.put(attributeName, buf.toString());
		}
		String setterName = setterNamesCache.get(attributeName);
		ClassInfo classInfo = classInfos.get(clazz);
		if (classInfo == null) {
			classInfo = new ClassInfo(clazz);
			classInfos.put(clazz, classInfo);
		}
		return classInfo.findSetters(setterName);
	}
	
	Iterable<Field> getAllTemplateOutlets(Class<?> clazz) {
		ClassInfo classInfo = classInfos.get(clazz);
		if (classInfo == null) {
			classInfo = new ClassInfo(clazz);
			classInfos.put(clazz, classInfo);
		}
		return classInfo.getAllTemplateOutlets();
	}
	
	Iterable<Method> getAllTemplateHandlers(Class<?> clazz) {
		ClassInfo classInfo = classInfos.get(clazz);
		if (classInfo == null) {
			classInfo = new ClassInfo(clazz);
			classInfos.put(clazz, classInfo);
		}
		return classInfo.getAllTemplateHandlers();
	}
	
}
